介绍


通过Form类自动生成form表单中所需要的HTML

创建Form类时,主要涉及到 【字段】 和 【插件(小部件)】,字段用于对用户请求数据的验证,插件用于自动生成HTML

Form组件的功能:

  • 通过Form类自动生成form表单中所需要的HTML
  • 对提交的数据进行验证,并且返回错误信息
  • 保留上次的输入内容

Form组件的注意事项:

  • Form组件只能渲染出form表单中所需要的HTML,不能将对应的数据自动填充到表单中(即:无法实现修改页面的自动填充数据效果

    • 解决办法:
      • 不使用form组件渲染form表单,自己编写form表单,form组件只用于验证
      • 不使用form组件,改为使用 modelForm

  • 不要认为用了Form组件就一定要用它的自动渲染form表单功能,Form组件只用于验证,不做form表单渲染也是可以的,因为最后都是从 request.POST 中获取数据进行验证,前提是 form表单中的 name 需要和 form类中的字段名一致

创建对应的表单所需的HTML


  • 通过 widgets 插件创建表单所需的HTML

  • 写法一 -> 常用 -> 三种写法效果都是一样的

from django import forms
from django.forms import widgets


# 创建表单类
class Form的类名(forms.Form):
'''
        字段名 -> 对应着 html 中 name='xxx'
        字段类 -> 对应着 html 中  type='xxx'
    '''
字段名 = forms.字段类名(
字段的参数=xxx,
字段的参数=xxx,
        widget=widgets.xxx()
    )

  • 写法二 -> 三种写法效果都是一样的

from django import forms
from django.forms import fields
from django.forms import widgets


# 创建表单类
class Form的类名(forms.Form):
'''
        字段名 -> 对应着 html 中 name='xxx'
        字段类 -> 对应着 html 中  type='xxx'
    '''
字段名 = fields.字段类名(
字段的参数=xxx,
字段的参数=xxx,
        widget=widgets.xxx()
    )

  • 写法三 -> 三种写法效果都是一样的

from django import forms
from django.forms import widgets


# 创建表单类
class Form的类名(forms.Form):
'''
        字段名 -> 对应着 html 中 name='xxx'
        字段类 -> 对应着 html 中  type='xxx'
    '''
字段名 = forms.fields.字段类名(
字段的参数=xxx,
字段的参数=xxx,
        widget=widgets.xxx()
    )

  • input -> 普通输入框

    • 写法一

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
username = forms.CharField(
        max_length=16,
        label='用户名',
    )

    • 写法二

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    username = forms.CharField(
        max_length=16,
        label='用户名',
        widget=widgets.TextInput()
    )

  • password -> 密码输入框

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    password = forms.CharField(
        max_length=16,
        label='密码',
        widget=widgets.PasswordInput()  # 使用 widgets 插件生成密码输入框
    )

  • textarea -> 文本域

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    article = forms.CharField(
        widget=widgets.Textarea(attrs={'id': 'article', 'cols': '50', 'rows': '30'})  # 如果 cols 和 rows 不传,默认为 {'cols': '40', 'rows': '10'}
    )

  • file -> 选择文件

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    img = forms.CharField(
        widget=widgets.FileInput()
    )

  • radio -> 单选按钮

    • radio的值必须为字符串

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    city = forms.ChoiceField(
        choices=[[1, '东莞'], [2, '广州'], [3, '深圳']],
        label='城市',
        initial=3,  # 默认选中第3个
        widget=widgets.RadioSelect()  # 使用 widgets 插件生成单选按钮
    )

  • select -> 下拉选框

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    city = forms.ChoiceField(
        choices=[[1, '东莞'], [2, '广州'], [3, '深圳']],
        label='城市',
        initial=3,  # 默认选中第3个
        widget=widgets.Select()  # 使用 widgets 插件生成下拉选框
    )

  • select -> 多选的下拉选框

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    city = forms.ChoiceField(
        choices=[[1, '东莞'], [2, '广州'], [3, '深圳']],
        label='城市',
        initial=[1, 3],  # 默认选中第1, 3个
        widget=widgets.SelectMultiple()  # 使用 widgets 插件生成多选下拉选框
    )

  • checkbox -> 单选框

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    city = forms.ChoiceField(
        label='是否记住密码',
        initial='checked',  # 默认选中
        widget=widgets.CheckboxInput()  # 使用 widgets 插件生成单选框
    )

  • checkbox -> 复选框

from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    city = forms.ChoiceField(
        choices=[[1, '东莞'], [2, '广州'], [3, '深圳']],
        label='城市',
        initial=[1, 3],  # 默认选中第1, 3个
        widget=widgets.CheckboxSelectMultiple()  # 使用 widgets 插件生成复选框
    )

  • ChoiceField字段的注意事项 -> 选择性标签实时更新问题

    • 在使用选择性标签时,需要注意choices的值可以是从数据库中获取到的值,但是由于是静态字段,获取的值无法实时更新,需要重写构造方法从而实现choice实时更新

    • 方法一

from .models import *
from django import forms
from django.forms import widgets


# 创建表单类
class RegFrom(forms.Form):
    city = forms.ChoiceField(
# choices=[[1, '东莞'], [2, '广州'], [3, '深圳']],
        label='城市',
        initial=3,  # 默认选中第3个
        widget=widgets.Select()  # 使用 widgets 插件生成下拉选框
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
self.fields['city'].choices = [[1, '东莞'], [2, '广州'], [3, '深圳'], [4, '茂名']]
        # 或
        self.fields['city'].choices = Employee.objects.all().values_list('id', 'province')  # 从数据库中获取数据,然后更新静态属性中的数据

    • 方法二

from .models import *
from django import forms
from django.forms import widgets
from django.forms import models as form_models


# 创建表单类
class RegFrom(forms.Form):
ChoiceField 方法接收一个列表或元祖
    city1 = forms.ChoiceField(choices=City.objects.all().values_list('pk', 'name'))  # 下拉选框

    • 方法二

      • 如果使用 ModelChoiceField 或 ModelMultipleChoiceField 方法最终显示的结果是 City Object,那么是因为 City 表没有配置 __str__ 方法

from .models import *
from django import forms
from django.forms import widgets
from django.forms import models as form_models


# 创建表单类
class RegFrom(forms.Form):
    # ModelChoiceField 和 ModelMultipleChoiceField 方法接收一个 QuerySet 对象
    city2 = forms.ModelChoiceField(queryset=City.objects.all().values_list('province'))  # 下拉选框
    city3 = forms.ModelMultipleChoiceField(queryset=City.objects.all().values_list('province'))  # 多选下拉选框

常用的字段参数


  • required

    • 是否不允许为空

    • 默认值: True

    • 如果不设置该参数,该字段默认不能为空

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    name = forms.CharField(
        required=False
    )

  • widget -> 插件

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    name = forms.CharField(
        widget=widgets.PasswordInput()
    )

  • label -> 用于生成Label标签或显示内容

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    name = forms.CharField(
        label='用户名',
    )

  • initial -> 初始值

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    name = forms.CharField(
        initial='初始值'
    )

  • error_messages -> 错误信息

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    name = forms.CharField(
        required=False,
        min_length=5,
        error_messages={
            'required': '不能为空',
            'min_length': '长度不能小于5'
        }
    )

  • validators -> 自定义验证规则

from django import forms
from django.forms import widgets
from django.core.validators import RegexValidator  # 导入 RegexValidator 验证器


class RegFrom(forms.Form):
    phone = forms.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

  • disabled -> 是否可以编辑

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    name = forms.CharField(
        disabled=True
    )

所有内置字段参数


Field
    required=True,                是否允许为空
    widget=None,                  HTML插件
    label=None,                   用于生成Label标签或显示内容
    initial=None,                 初始值
    help_text='',                 帮助信息(在标签旁边显示)
    error_messages=None,          错误信息 {'required': '不能为空', 'invalid': '格式错误'}
    validators=[],                自定义验证规则
    localize=False,               是否支持本地化
    disabled=False,               是否可以编辑
    label_suffix=None             Label内容后缀


CharField(Field)
    max_length=None,              最大长度
    min_length=None,              最小长度
    strip=True                    是否移除用户输入空白


IntegerField(Field)
    max_value=None,               最大值
    min_value=None,               最小值


FloatField(IntegerField)
...


DecimalField(IntegerField)
    max_value=None,               最大值
    min_value=None,               最小值
    max_digits=None,              总长度
    decimal_places=None,          小数位长度


BaseTemporalField(Field)
    input_formats=None           时间格式化  


DateField(BaseTemporalField)     格式:2015-09-01
TimeField(BaseTemporalField)     格式:11:12
DateTimeField(BaseTemporalField) 格式:2015-09-01 11:12


DurationField(Field)             时间间隔:%d %H:%M:%S.%f
...


RegexField(CharField)
    regex,                       自定制正则表达式
    max_length=None,             最大长度
    min_length=None,             最小长度
    error_message=None,          忽略,错误信息使用 error_messages={'invalid': '...'}


EmailField(CharField)      
...


FileField(Field)
    allow_empty_file=False      是否允许空文件


ImageField(FileField)      
...
    注:需要PIL模块,pip3 install Pillow
    以上两个字典使用时,需要注意两点:
        - form表单中 enctype="multipart/form-data"
        - view函数中 obj = MyForm(request.POST, request.FILES)


URLField(Field)
...


BooleanField(Field)  
 ...


NullBooleanField(BooleanField)
 ...


ChoiceField(Field)
 ...
    choices=(),                 选项,如:choices = ((0,'上海'),(1,'北京'),)
    required=True,              是否必填
    widget=None,                插件,默认select插件
    label=None,                 Label内容
    initial=None,               初始值
    help_text='',               帮助提示


ModelChoiceField(ChoiceField)
    ...                         django.forms.models.ModelChoiceField
    queryset,                   查询数据库中的数据
    empty_label="---------",    默认空显示内容
    to_field_name=None,         HTML中value的值对应的字段
    limit_choices_to=None       ModelForm中对queryset二次筛选


ModelMultipleChoiceField(ModelChoiceField)
...                         django.forms.models.ModelMultipleChoiceField


TypedChoiceField(ChoiceField)
    coerce = lambda val: val    对选中的值进行一次转换
    empty_value= ''             空值的默认值


MultipleChoiceField(ChoiceField)
...


TypedMultipleChoiceField(MultipleChoiceField)
    coerce = lambda val: val    对选中的每一个值进行一次转换
    empty_value= ''             空值的默认值


ComboField(Field)
    fields=()                   使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                                fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])


MultiValueField(Field)
    PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用


SplitDateTimeField(MultiValueField)
    input_date_formats=None,    格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
    input_time_formats=None     格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']


FilePathField(ChoiceField)      文件选项,目录下文件显示在页面中
    path,                       文件夹路径
    match=None,                 正则匹配
    recursive=False,            递归下面的文件夹
    allow_files=True,           允许文件
    allow_folders=False,        允许文件夹
    required=True,
    widget=None,
    label=None,
    initial=None,
    help_text=''


GenericIPAddressField
    protocol='both',            both,ipv4,ipv6支持的IP格式
    unpack_ipv4=False           解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1, PS:protocol必须为both才能启用


SlugField(CharField)            数字,字母,下划线,减号(连字符)
    ...


UUIDField(CharField)            uuid类型

插件的相关参数


  • attrs -> 用于定义相关的id class 或 html 中的自定义属性

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    username = forms.CharField(
        widget=widgets.TextInput(attrs={'id': 'user', 'class': 'username red'})
    )
    password = forms.CharField(
        widget=widgets.PasswordInput(attrs={'id': 'password'})
    )

  • render_value -> 只作用于PasswordInput下,当验证失败时刷新页面后,是否将原本的值填写回去,其他widgets下的方法都会把值自动填写回去

from django import forms
from django.forms import widgets


class RegFrom(forms.Form):
    password = forms.CharField(
        widget=widgets.PasswordInput(render_value=True)
    )

批量添加样式


  • 批量添加样式就是Form类下的所有字段都会拥有该样式

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.CharField(
        label='用户名',
        max_length=16,
        widget=widgets.TextInput()
    )
    password = forms.CharField(
        label='密码',
        widget=widgets.PasswordInput(render_value=True),
    )
    confirm_password = forms.CharField(
        label='确认密码',
        widget=widgets.PasswordInput(render_value=True),
    )
    city = forms.CharField(
        label='城市',
        initial=1,
        widget=widgets.Select()
    )

# 通过重写form类的init方法实现批量添加样式
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        for field in iter(self.fields):
            self.fields[field].widget.attrs.update({
                'class': 'form-control'
            })

自定义校验


  • 在字段参数中的 required、max_length、min_length 等,它会在一定层度上可以帮我们完成一些简单的校验,但是如果是一些复杂的验证如:身份证验证/电话号码,那么就需要通过正则来进行验证

1.RegexValidator验证器

# RegexValidator('正则表达式', '错误信息')

from django import forms
from django.forms import widgets
from django.core.validators import RegexValidator  # 导入 RegexValidator 验证器


class RegFrom(forms.Form):
    phone = forms.CharField(
        validators=[RegexValidator(r'^[0-9]+$', '请输入数字'), RegexValidator(r'^159[0-9]+$', '数字必须以159开头')],
    )

2.自定义验证函数

import re
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


# 自定义验证规则
def mobile_validate(value):
    mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
    if not mobile_re.match(value):
        raise ValidationError('手机号码格式错误')  # 一定要使用 raise 抛出异常,且一定要使用 ValidationError 方法指定错误信息


class RegFrom(forms.Form):
    phone = forms.CharField(
        validators=[mobile_validate],
    )

cleaned_data  和 errors


  • .cleaned_data 存放着所有验证通过字段数据

  • .errors 存放着所有字段错误信息,有时候打印出来的是一段HTML代码,但是如果将 .errors 发送给前端,那么前端所接受到的是一个form字段错误信息的数组

Hook方法(钩子函数)


  • 什么是钩子函数: Django源码通过反射找到指定前缀的函数并且执行该函数,即: 该指定前缀的函数就是钩子函数

  • 什么时候使用钩子函数: 当Form组件所提供的校验规则无法满足你的校验,那么可以使用钩子函数自定义校验规则

1.局部钩子函数

  • 使用场景: 对某一个字段进行校验,如: 在注册的时候判断用户名字段是否已经被注册过了

  • 在Form类中定义 clean_字段名() 方法,就能够实现对特定字段进行校验

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.CharField(
        min_length=8,
        label="用户名",
        initial="张三",
        error_messages={
            "required": "不能为空",
            "invalid": "格式错误",
            "min_length": "用户名最短8位"
        },
        widget=forms.widgets.TextInput(attrs={"class": "form-control"})
    )

# 定义局部钩子(clean_字段名() 方法),用来校验username字段
    def clean_username(self):
# 此时 通过检验的字段的数据都保存在 self.cleaned_data
        value = self.cleaned_data.get("username")
        if "666" in value:
            raise ValidationError("光喊666是不行的")
        else:
            return value

2. 全局钩子函数

  • 使用场景: 获取所有已经通过验证的字段,然后取某几个字段进行二次判断,如: 校验两次密码是否一致

  • 全局钩子的说明: 当全部校验通过后才会执行clean()方法,且在clean()方法中一定可以获取到所有已经通过验证的数据

  • 在Form类中定义 clean() 方法,就能够实现对字段进行全局校验

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
# 密码
    password = forms.CharField(
        min_length=6,
        label="密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )

# 确认密码
    re_password = forms.CharField(
        min_length=6,
        label="确认密码",
        widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True)
    )

# 定义全局的钩子,用来校验密码和确认密码字段是否相同
    def clean(self):
# 此时 通过检验的字段的数据都保存在 self.cleaned_data
        password_value = self.cleaned_data.get('password')
        re_password_value = self.cleaned_data.get('re_password')
        if password_value == re_password_value:
            return self.cleaned_data
        else:
            self.add_error('re_password', '两次密码不一致')  # 定义在哪一个字段显示该条错误信息
            raise ValidationError('两次密码不一致')

form对象的常用属性


1.该栏目所用到的表

from django import forms
from django.forms import widgets

class RegFrom(forms.Form):
    username = forms.CharField(
        label='用户名',
        max_length=16,
        initial='初始值',
        widget=widgets.TextInput(),
        error_messages={
            'required': '用户名不能为空',
        }
    )
    password = forms.CharField(
        label='密码',
        widget=widgets.PasswordInput(render_value=True),
        error_messages={
            'required': '密码不能为空',
        }
    )

2.BoundField对象(form对象)

  • BoundField对象的说明:

    • form组件类所实例化出来的对象下的xxx对象(即: RegFrom类所实例化出来的对象下的 username 对象或 password 对象)
    • 通俗理解: form组件类所实例化出来的对象存储着一个个的form对象,即: BoundField对象
    • 在模板中循环form组件类所实例化出来的对象所得到的就是BoundField对象
    • BoundField对象不要和Field对象(字段对象)还有Form类所实例化出来的对象搞混了

  • 获取BoundField对象(form对象)的方法

    • 方法一 -> 获取指定的BoundField对象(form对象)

# form组件类所实例化出来的对象['xxx']

form = RegFrom()
boundField_obj = form['username']

print(boundField_obj)  # <input type="text" name="username" value="初始值" maxlength="16" required id="id_username" /> -> 因为 BoundField 类设置了 __str__
print(type(boundField_obj))  # <class 'django.forms.boundfield.BoundField'>

    • 方法二 -> 循环form组件类所实例化出来的对象

form = RegFrom()

for form_obj in form:
    print(form_obj)  # <input type="text" name="username" value="初始值" maxlength="16" required id="id_username" /> -> 因为 BoundField 类设置了 __str__
    print(type(form_obj))  # <class 'django.forms.boundfield.BoundField'>

  • BoundField对象(form对象)的常用属性和方法

form = RegFrom()
for boundField_obj in form:
    print(boundField_obj)  # 直接打印当前form对象(即:boundField对象)会获取到当前对象所对应的表单标签,因为 BoundField 类设置了 __str__ -> <input type="text" name="username" value="初始值" id="id_username" maxlength="16" required />
    print(boundField_obj.form)  # form表单的html -> <tr><th><label for="id_username">用户名:</label> …… </tr>
    print(boundField_obj.field)  # 字段对象(即: form组件的字段类所实例化出来的对象) -> <django.forms.fields.CharField object at 0x000001F469D8E358>
    print(boundField_obj.name)  # form对象(即:boundField对象)所对应的名字 -> username
    print(boundField_obj.html_name)  # form对象(即:boundField对象)所对应的表单标签中 name 的名字 -> username
    print(boundField_obj.label)  # label的名字 -> 用户名
    print(boundField_obj.id_for_label)  # 获取label应该设置的对应input的id -> id_username
    print(boundField_obj.label_tag())  # 获取label的html标签 -> <label for="id_username">用户名:</label>
    print(boundField_obj.value())  # 返回当前form对象(即:boundField对象)所对应的表单标签所填写的值,如果没有就返回 initial='xxx' 所设置的初始值,如果没有设置就返回 None -> 初始值
    print(boundField_obj.data)  # 返回当前form对象(即:boundField对象)所对应的表单标签所填写的值,如果没有就返回 None
    print(boundField_obj.errors)  # 验证过后,form对象(即:boundField对象)所对应的错误信息
    print(boundField_obj.is_hidden)  # 判断当前form对象(即:boundField对象)所对应的表单标签是否是隐藏标签

  • BoundField对象(form对象)的所有属性与方法

# 在 BoundField 中有form对象的所有属性与方法

from django.forms.boundfield import BoundField

3.Field对象 -> 字段对象

  • Field对象(字段对象) -> form组件下的字段类所实例化出来的字段对象

  • 获取Field对象(字段对象)的方法

    • 方法一: 获取指定的Field对象(字段对象)

# form组件类所实例化出来的对象.fields['xxx']

form = RegFrom()

print(form.fields['username'])  # <django.forms.fields.CharField object at 0x00000241BA6BE320>
print(form.fields['password'])  # <django.forms.fields.CharField object at 0x000001CDFA64E358>

    • 方法二: 获取指定的Field对象(字段对象)

# form组件类所实例化出来的对象['xxx'].field

form = RegFrom()

print(form['username'].field)  # <django.forms.fields.CharField object at 0x00000241BA6BE320>
print(form['password'].field)  # <django.forms.fields.CharField object at 0x000001CDFA64E358>

    • 方法三: 循环获取所有的Field对象(字段对象)

form = RegFrom()

for boundField_obj in form:
    print(boundField_obj.field)

  • 通过字段对象可以获取该字段所设置的相关参数

form = RegFrom()

print(form.fields['username'].label)  # 用户名
print(form.fields['username'].max_length)  # 16
print(form.fields['username'].initial)  # 初始值

模板相关


1. 将Form类所生成的HTML发送到模板中

# views.py

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.CharField(
        label='用户名',
        min_length=3,
        error_messages={
            'required': '用户名不能为空',
            'min_length': '用户名长度不能小于3',
        }
    )


def registered(request):
    form_obj = RegFrom()  # 实例化一个表单对象
    if request.method == 'POST':
        form_obj = RegFrom(request.POST)  # 将post过来的数据再次传入Form类中,然后对数据进行验证,如果数据有误,所实例化出来的form_obj就会拿到该条数据有误的相关信息(即:错误信息 error_messages)
print(form_obj.errors)  # 存放着所有form字段错误信息,虽然打印出来的是段HTML代码,但是如果将 .errors 发送给前端,那么前端所接受到的是一个form字段错误信息的数组
        if form_obj.is_valid():  # 判断post过来的数据是否有误
            print(form_obj.cleaned_data)  # {'username': 'Aimer'} 所有经过验证的字段都保存在 .cleaned_data 里
            del form_obj.cleaned_data['confirm_password']  # 删除 form_obj.cleaned_data 中数据库表不需要的数据
            Student.objects.create(**form_obj.cleaned_data)  # 将数据保存到数据库中
            return HttpResponse('注册成功')
    return render(request, 'registered.html', {'form_obj': form_obj})

2. as_p 

  • 将Form类生成的表单所需的HTML渲染到p标签中

<form action="/registered/" method="post" novalidate>
    {% csrf_token %}
    {{ form_obj.as_p }}
    <input type="submit" value="提交">
</form>

3. as_ul

  • 将Form类生成的表单所需的HTML渲染到li标签中

<form action="/registered/" method="post" novalidate>
    {% csrf_token %}
    <ul>
        {{ form_obj.as_ul }}
    </ul>
    <input type="submit" value="提交">
</form>

4. as_table

  • 将Form类生成的表单所需的HTML渲染到table标签中

<form action="/registered/" method="post" novalidate>
    {% csrf_token %}
    <table>
        {{ form_obj.as_table }}
    </table>
    <input type="submit" value="提交">
</form>

5. 自定义

<form action="/registered/" method="post" novalidate>
    {% csrf_token %}
    <div class="form-group">
 <!-- 获取label的名称,不同的Django版本返回不同的结果,结果一: 返回带label标签的label内容,结果二: 直接返回label内容,不带label标签 -->
        <!-- id_for_label 的意思: 将form_obj.username所生成的html中的id放在该label中for标签属性中 -->
        <lable for="{{ form_obj.username.id_for_label }}">{{ form_obj.username.label }}</lable>
<!-- 获取相关的表单标签 -->
        {{ form_obj.username }}
<!-- 获取第一条验证过后的错误信息 -->
        <span class="help-block">{{ form_obj.username.errors.0 }}</span>
    </div>
    <input type="submit" value="提交">
</form>

6. 循环生成生成表单所需的HTML

<form action="/registered/" method="post" novalidate>
    {% csrf_token %}

 <!-- 直接对返回的form对象进行循环 -->
    {% for field in form_obj %}
        <div class="form-group {% if field.errors.0 %}has-error{% endif %}">
            <lable for="{{ field.id_for_label }}">{{ field.label }}</lable>
            {{ field }}
            <span class="help-block">{{ field.errors.0 }}</span>
        </div>
    {% endfor %}
    <div class="form-group">
        <input type="submit" class="btn btn-default">
    </div>
</form>

只用Form组件进行验证,不做渲染


  • 不要认为用了Form组件就一定要用它的自动渲染form表单功能,Form组件只用于验证,不做form表单渲染也是可以的,因为最后都是从 request.POST 中获取数据进行验证,前提是 form表单中的 name 需要和 form类中的字段名一致

  • 只用Form组件进行验证,不做渲染(即: 自己编写form表单) -> 使用 form 表单提交数据

# views.py

from django.shortcuts import render, HttpResponse, redirect

from .models import *

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.fields.CharField(
        label='用户名',
        min_length=3,
        error_messages={
            'required': '用户名不能为空',
            'min_length': '用户名长度不能小于3',
        }
    )
    password = forms.CharField(
        label='密码',
        min_length=3,
        error_messages={
            'required': '用户名不能为空',
            'min_length': '用户名长度不能小于3',
        }
    )


def registered(request):
    form_obj = RegFrom()  # 实例化一个表单对象
    if request.method == 'POST':
        form_obj = RegFrom(request.POST)  # 将post过来的数据再次传入Form类中,然后对数据进行验证,如果数据有误,所实例化出来的form_obj就会拿到该条数据有误的相关信息(即:错误信息 error_messages)

        if form_obj.is_valid():  # 判断post过来的数据是否有误
            print(form_obj.cleaned_data)  # {'username': 'Aimer', 'password': '123'} 所有经过验证的字段都保存在 .cleaned_data 里
            Student.objects.create(**form_obj.cleaned_data)  # 将数据保存到数据库中
            return HttpResponse('注册成功')
        else:
            return HttpResponse('注册失败')
    return render(request, 'registered.html')

# models.py

class Student(models.Model):
    username = models.CharField(max_length=10)
    password = models.CharField(max_length=16)

# xxx.html

<form action="/registered/" method="post" novalidate>
    {% csrf_token %}
    <div class="form-group">
        <lable for="username">用户名</lable>
        <input type="text" id="username" class="form-control" name="username">
    </div>
    <div class="form-group">
        <lable for="password">密码</lable>
        <input type="password" id="password" class="form-control" name="password">
    </div>
    <input type="submit" class="btn btn-default" value="提交">
</form>

  • 只用Form组件进行验证,不做渲染(即: 自己编写form表单) -> 使用ajax提交数据

# views.py

from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse

from .models import *

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.fields.CharField(
        label='用户名',
        min_length=3,
        error_messages={
            'required': '用户名不能为空',
            'min_length': '用户名长度不能小于3',
        }
    )
    password = forms.CharField(
        label='密码',
        min_length=3,
        error_messages={
            'required': '用户名不能为空',
            'min_length': '用户名长度不能小于3',
        }
    )


def registered(request):
    form_obj = RegFrom()  # 实例化一个表单对象
    if request.method == 'POST':
        form_obj = RegFrom(request.POST)  # 将post过来的数据再次传入Form类中,然后对数据进行验证,如果数据有误,所实例化出来的form_obj就会拿到该条数据有误的相关信息(即:错误信息 error_messages)

        if form_obj.is_valid():  # 判断post过来的数据是否有误
            print(form_obj.cleaned_data)  # {'username': 'Aimer', 'password': '123'} 所有经过验证的字段都保存在 .cleaned_data 里
            Student.objects.create(**form_obj.cleaned_data)  # 将数据保存到数据库中
            return JsonResponse({
                'msg': '注册成功'
            })
        else:
            return JsonResponse({
                'msg': '注册失败',
                'error_msg': form_obj.errors  # 获取验证后的错误信息
            })
    return render(request, 'registered.html')

# xxx.html

<form novalidate>
    {% csrf_token %}
    <div class="form-group">
        <lable for="username">用户名</lable>
        <input type="text" id="username" class="form-control" name="username">
    </div>
    <div class="form-group">
        <lable for="password">密码</lable>
        <input type="password" id="password" class="form-control" name="password">
    </div>
    <button type="button" class="btn btn-default" id="btn-submit">提交</button>
</form>

<script>
    $(function () {
        $('#btn-submit').click(function () {
            var username = $('#username').val(),
                password = $('#password').val(),
                csrf_token = $('[name="csrfmiddlewaretoken"]').val();
            $.ajax({
                url: '/registered/',
                type: 'post',
                data: {
                    username: username,
                    password: password,
                    csrfmiddlewaretoken: csrf_token
                },
                success: function (res) {
                    console.log(res)
                }
            })
        })
    })
</script>

注册例子


  • 使用表单提交 + 使用form组件渲染form表单

form_m.rar

# views.py

from django.shortcuts import render, HttpResponse, redirect

from .models import *

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.CharField(
        label='用户名',
        max_length=16,
        error_messages={
            'required': '用户名不能为空',
            'max_length': '用户名长度不能超过16位',
        },
        widget=widgets.TextInput(attrs={'class': 'form-control'})
    )
    password = forms.CharField(
        label='密码',
        min_length=6,
        max_length=10,
        widget=widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True),
        error_messages={
            'required': '密码不能为空',
            'min_length': '密码不能小于6位',
            'max_length': '密码不能超过10位',
        }
    )
    confirm_password = forms.CharField(
        label='确认密码',
        min_length=6,
        max_length=10,
        widget=widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True),
        error_messages={
            'required': '密码不能为空',
            'min_length': '密码不能小于6位',
            'max_length': '密码不能超过10位',
        }
    )
    city = forms.CharField(
        label='城市',
        initial=1,
        widget=widgets.Select(attrs={'class': 'form-control'})
    )

# 由于是静态字段,获取的值无法实时更新,需要重写构造方法从而实现choice实时更新
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["city"].widget.choices = City.objects.all().values_list("id", "name")

    def clean(self):
# 此时 通过检验的字段的数据都保存在 self.cleaned_data
        pwd = self.cleaned_data.get("password")
        re_pwd = self.cleaned_data.get("confirm_password")
        if pwd != re_pwd:
            self.add_error("confirm_password", ValidationError("两次密码不一致"))
            raise ValidationError("两次密码不一致")
        return self.cleaned_data


def registered(request):
    form_obj = RegFrom()  # 实例化一个表单对象
    if request.method == 'POST':
        form_obj = RegFrom(request.POST)  # 将post过来的数据再次传入Form类中,然后对数据进行验证,如果数据有误,所实例化出来的form_obj就会拿到该条数据有误的相关信息(即:错误信息 error_messages)
print(form_obj.errors)  # 存放着所有form字段错误信息,虽然打印出来的是段HTML代码,但是如果将 .errors 发送给前端,那么前端所接受到的是一个form字段错误信息的数组
        if form_obj.is_valid():  # 判断post过来的数据是否有误
            print(form_obj.cleaned_data)  # {'city': '3', 'username': 'Aimer', 'confirm_password': '123456', 'password': '123456'} 所有经过验证的字段都保存在 .cleaned_data 里
            del form_obj.cleaned_data['confirm_password']  # 删除 form_obj.cleaned_data 中数据库表不需要的数据
            Student.objects.create(**form_obj.cleaned_data)  # 将数据保存到数据库中
            return HttpResponse('注册成功')
    return render(request, 'registered.html', {'form_obj': form_obj})

# models.py

from django.db import models

class Student(models.Model):
    username = models.CharField(max_length=10)
    password = models.CharField(max_length=16)
    city = models.CharField(max_length=10)

# registered.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Title</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <script type=text/javascript src="{% static 'jquery-3.3.1.js' %}"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form action="/registered/" method="post" novalidate>
                {% csrf_token %}
                <div class="form-group {% if form_obj.username.errors.0 %}has-error{% endif %}">
                    {{ form_obj.username.label }}
                    {{ form_obj.username }}
                    <span class="help-block">{{ form_obj.username.errors.0 }}</span>
                </div>
                <div class="form-group {% if form_obj.password.errors.0 %}has-error{% endif %}">
                    {{ form_obj.password.label }}
                    {{ form_obj.password }}
                    <span class="help-block">{{ form_obj.password.errors.0 }}</span>
                </div>
                <div class="form-group {% if form_obj.confirm_password.errors.0 %}has-error{% endif %}">
                    {{ form_obj.confirm_password.label }}
                    {{ form_obj.confirm_password }}
                    <span class="help-block">{{ form_obj.confirm_password.errors.0 }}</span>
                </div>
                <div class="form-group {% if form_obj.city.errors.0 %}has-error{% endif %}">
                    {{ form_obj.city.label }}
                    {{ form_obj.city }}
                    <span class="help-block">{{ form_obj.city.errors.0 }}</span>
                </div>
                <div class="form-group">
                    <input type="submit" class="btn btn-default">
                </div>
            </form>
        </div>
    </div>
</div>
</body>
</html>

  • 使用 ajax 提交 + 自己编写form表单

ajax_form_m.rar

# views.py

from django.shortcuts import render, HttpResponse, redirect
from django.http import JsonResponse

from .models import *

from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError


class RegFrom(forms.Form):
    username = forms.CharField(
        label='用户名',
        max_length=16,
        error_messages={
            'required': '用户名不能为空',
            'max_length': '用户名长度不能超过16位',
        },
        widget=widgets.TextInput(attrs={'class': 'form-control'})
    )
    password = forms.CharField(
        label='密码',
        min_length=6,
        max_length=10,
        widget=widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True),
        error_messages={
            'required': '密码不能为空',
            'min_length': '密码不能小于6位',
            'max_length': '密码不能超过10位',
        }
    )
    confirm_password = forms.CharField(
        label='确认密码',
        min_length=6,
        max_length=10,
        widget=widgets.PasswordInput(attrs={'class': 'form-control'}, render_value=True),
        error_messages={
            'required': '密码不能为空',
            'min_length': '密码不能小于6位',
            'max_length': '密码不能超过10位',
        }
    )
    city = forms.CharField(
        label='城市',
        initial=1,
        widget=widgets.Select(attrs={'class': 'form-control'}),
        error_messages={
            'required': '城市不能为空',
        }
    )

 # 由于是静态字段,获取的值无法实时更新,需要重写构造方法从而实现choice实时更新
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["city"].widget.choices = City.objects.all().values_list("id", "name")

    def clean(self):
# 此时 通过检验的字段的数据都保存在 self.cleaned_data
        pwd = self.cleaned_data.get("password")
        re_pwd = self.cleaned_data.get("confirm_password")
        if pwd != re_pwd:
            self.add_error("confirm_password", ValidationError("两次密码不一致"))
            raise ValidationError("两次密码不一致")
        return self.cleaned_data


def registered(request):
    form_obj = RegFrom()  # 实例化一个表单对象
    if request.method == 'POST':
        form_obj = RegFrom(request.POST)  # 将post过来的数据再次传入Form类中,然后对数据进行验证,如果数据有误,所实例化出来的form_obj就会拿到该条数据有误的相关信息(即:错误信息 error_messages)
        if form_obj.is_valid():  # 判断post过来的数据是否有误
            print(form_obj.cleaned_data)  # {'city': '3', 'username': 'Aimer', 'confirm_password': '123456', 'password': '123456'} 所有经过验证的字段都保存在 form_obj.cleaned_data 里
            del form_obj.cleaned_data['confirm_password']  # 删除 form_obj.cleaned_data 中数据库表不需要的数据
            Student.objects.create(**form_obj.cleaned_data)  # 将数据保存到数据库中
            return JsonResponse({
                'status': 1,
                'msg': '注册成功'
            })
        else:
            return JsonResponse({
                'status': 0,
                'msg': '注册失败',
                'error_msg': form_obj.errors  # 获取验证后的错误信息
            })
    return render(request, 'registered.html', {'form_obj': form_obj})

# models.py

from django.db import models

class Student(models.Model):
    username = models.CharField(max_length=10)
    password = models.CharField(max_length=16)
    city = models.CharField(max_length=10)

# registered.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <title>Title</title>
    <meta name="description" content="">
    <meta name="keywords" content="">
    {% load static %}
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <script type=text/javascript src="{% static 'jquery-3.3.1.js' %}"></script>
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-md-offset-3">
            <form novalidate>
                {% csrf_token %}
                <div class="form-group">
                    <label for="username">用户名</label>
                    <input type="text" id="username" class="form-control" name="username">
                    <span class="help-block"></span>
                </div>
                <div class="form-group">
                    <label for="username">密码</label>
                    <input type="text" id="password" class="form-control" name="password">
                    <span class="help-block"></span>
                </div>
                <div class="form-group">
                    <label for="confirm_password">确认密码</label>
                    <input type="text" id="confirm_password" class="form-control" name="confirm_password">
                    <span class="help-block"></span>
                </div>
                <div class="form-group">
                    <label for="city">城市</label>
                    <input type="text" id="city" class="form-control" name="city">
                    <span class="help-block"></span>
                </div>
                <div class="form-group">
                    <button type="button" id="btn-submit" class="btn btn-default">提交</button>
                </div>
            </form>
        </div>
    </div>
</div>
<script src="/static/jquery-3.3.1.js"></script>
<script>
    $(function () {
        $('#btn-submit').click(function () {
            var username = $('#username').val(),
                password = $('#password').val(),
                confirm_password = $('#confirm_password').val(),
                city = $('#city').val(),
                csrf_token = $('[name="csrfmiddlewaretoken"]').val();
            $('.form-group').removeClass('has-error').find('.help-block').html('');
            $.ajax({
                url: '/registered/',
                type: 'post',
                data: {
                    username: username,
                    password: password,
                    confirm_password: confirm_password,
                    city: city,
                    csrfmiddlewaretoken: csrf_token
                },
                success: function (res) {
                    if (res.status) {
                        alert(res.msg);
                        $('.form-group input').val('')
                    } else {
                        $.each(res.error_msg, function (k, v) {
                            console.log(k, v);
                            $('#' + k).next('.help-block').html(v).parents('.form-group').addClass('has-error')
                        })
                    }
                }
            })
        })
    })
</script>
</body>
</html>

formset 批量操作


  • forms.formset_factory(form=form类, extra=显示多少行数据) -> 返回值: 返回一个新的 form 类

  • extra 参数说明

    • 添加数据: 当 extra=10,那么就会显示 10 行输入框
    • 修改数据: 当 extra=0,那么就是根据 initial 参数所传入的初始化数据长度而显示多少行数据

# views/add_data.py

from django import forms
from django.shortcuts import render, HttpResponse
from app01.models import *


class AddUserForm(forms.Form):
    username = forms.CharField(label='用户名')
    phone = forms.CharField(label='电话号码')
    age = forms.CharField(label='年龄')


def add_data(request):
    old_form = AddUserForm()

AddUserFormSet = forms.formset_factory(AddUserForm, extra=10)
    formset = AddUserFormSet()

    if request.method == 'POST':
        formset = AddUserFormSet(request.POST)
        if formset.is_valid():
            print(formset.cleaned_data)  # [{'age': '22', 'username': 'Amy', 'phone': '13038802402'}, {}, ...]
            for row in formset.cleaned_data:
                if row:
                    UserInfo.objects.create(**row)
            return HttpResponse('验证成功')
        else:
            print('验证失败')

    return render(request, 'change.html', {
        'old_form': old_form,
        'formset': formset
    })

# views/edit_data.py

from django import forms
from django.shortcuts import render, HttpResponse
from app01.models import *


class EditUserForm(forms.Form):
    id = forms.CharField(label='id')
    username = forms.CharField(label='用户名')
    phone = forms.CharField(label='电话号码')
    age = forms.CharField(label='年龄')


def edit_data(request):
    old_form = EditUserForm()

EditUserFormSet = forms.formset_factory(EditUserForm, extra=0)
# formset = EditUserFormSet(initial=[
    #     {'id': 1, 'username': 'Kevin', 'phone': 13022024070, 'age': '12'},
    #     {'id': 2, 'username': 'Yeung', 'phone': 13022024070, 'age': '22'},
    #     {'id': 3, 'username': 'Amy', 'phone': 13022024070, 'age': '32'},
    # ])
    user_list = UserInfo.objects.all().values()
    formset = EditUserFormSet(initial=user_list)

    if request.method == 'POST':
        formset = EditUserFormSet(request.POST)
        if formset.is_valid():
            print(formset.cleaned_data)  # [{'id': '1', 'age': '22', 'username': 'Amy', 'phone': '13038802402'}, {}, ...]
            for row in formset.cleaned_data:
                UserInfo.objects.filter(pk=row['id']).update(**row)
            return HttpResponse('验证成功')
        else:
            print('验证失败')

    return render(request, 'change.html', {
        'old_form': old_form,
        'formset': formset
    })

# change.html

<form method="post" novalidate>
    {% csrf_token %}
{{ formset.management_form }}
    <table border="1">
        <thead>
        <tr>
            {% for filed in old_form %}
                {% if filed.name != 'id' %}
                    <th>{{ filed.label }}</th>
                {% endif %}
            {% endfor %}
        </tr>
        </thead>
        <tbody>
        {% for form in formset %}
            <tr>
                {% for filed in form %}
                    {% if filed.name != 'id' %}
                        <td>{{ filed }} {{ filed.errors.0 }}</td>
                    {% else %}
                        <td style="display: none">{{ filed }} {{ filed.errors.0 }}</td>
                    {% endif %}
                {% endfor %}
            </tr>
        {% endfor %}
        </tbody>
    </table>
    <input type="submit" value="提交">
</form>

  • 添加数据


  • 修改数据